package org.fusionide.builder.parsers.cfml;

import java.io.IOException;
import java.io.InputStream;
import java.util.Stack;
import java.util.Vector;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.fusionide.builder.parsers.IParser;
import org.fusionide.builder.parsers.IParserHandler;
import org.fusionide.builder.parsers.IRootParserContext;
import org.fusionide.builder.parsers.ParserException;
import org.fusionide.builder.parsers.cfml.tags.CFPage;
import org.fusionide.util.RegEx;


public class CFMLParser implements IParser {

	private StringBuffer parserBuffer = new StringBuffer();

	private StringBuffer fileBuffer = new StringBuffer();

	private Character[] startTag = new Character[] { '<' };

	private Character[] endTag = new Character[] { '>' };

	private Character[] breakTag = new Character[] { '<' };

	private int currentState = NotInTag;

	private String cfTagPattern = "<cf";

	private String cfCommentPattern = "<!---.*?--->";

	private String cfStartComment = "<!---";

	private String tagPattern = "(</(cf\\w+)>|<(cf\\w*)\\s*(.*?)\\s*(/|)>)";

	private static final int NotInTag = 0;

	private static final int InTag = 1;

	private static final int InCFComment = 2;

	CFMLTag rootTag;

	private void flushParserBuffer() {
		fileBuffer.append(parserBuffer);
		parserBuffer.setLength(0);
	}

	public int getLineNumber(int offset) {
		return getLineNumber(offset, -1, 0);
	}

	public int getLineNumber(int offset, int start, int currentLine) {
		int newLineIndex = start;
		if (newLineIndex >= offset)
			return currentLine;
		newLineIndex = fileBuffer.indexOf(RegEx.newline, newLineIndex + 1);
		if (newLineIndex == -1)
			return currentLine <= 0 ? 1 : currentLine + 1;
		currentLine++;
		return getLineNumber(offset, newLineIndex, currentLine);
	}

	public void build(IFile file, IParserHandler parserHandler) {
		InputStream inputStream;
		try {
			inputStream = file.getContents();
			byte[] fileBytes = new byte[inputStream.available()];
			inputStream.read(fileBytes);
			parse(fileBytes, parserHandler);
		} catch (CoreException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public IRootParserContext parse(byte[] input, IParserHandler parserHandler) {
		char[] charInput = new char[input.length];
		for (int i = 0; i < input.length; i++) {
			charInput[i] = (char) input[i];
		}
		return parse(charInput, parserHandler);
	}

	public IRootParserContext parse(char[] input, IParserHandler parserHandler) {
		int tagStartOffset = 0;
		Stack<CFMLTag> tagStack = new Stack<CFMLTag>();

		rootTag = null;
		try {
			parserBuffer = new StringBuffer();

			tagStack.push(new CFPage());

			for (int fileOffset = 0; fileOffset < input.length; fileOffset++) { // Loop
				Character currentCharacter = input[fileOffset];
				parserBuffer.append(currentCharacter);

				if (InCFComment == (InCFComment & currentState)) {
					RegEx commentTag = RegEx.Compile(cfCommentPattern, "s").match(parserBuffer.toString());
					if (commentTag.next()) {
						currentState = NotInTag;
						continue;
					}
				}
				if (InTag == (InTag & currentState) && parserBuffer.toString().startsWith(cfStartComment)) {
					currentState = InCFComment;
					continue;
				}

				if (InTag == (InTag & currentState)) {
					for (int breakTagIndex = 0; breakTagIndex < breakTag.length; breakTagIndex++) {
						if (currentCharacter.charValue() == breakTag[breakTagIndex]) {
							if (parserBuffer.toString().matches(cfTagPattern)) {
								parserHandler
										.addMarker(new ParserException("Unclosed tag", ParserException.SEVERITY_ERROR, getLineNumber(fileOffset)));
							}
							if (parserBuffer.toString().matches(cfCommentPattern)) {
								break;
							}
							currentState = NotInTag;
							break;
						}
					}
				}

				if (InTag == (InTag & currentState)) {
					for (int endTagIndex = 0; endTagIndex < endTag.length; endTagIndex++) {
						if (currentCharacter.charValue() == endTag[endTagIndex]) {

							RegEx tag = RegEx.Compile(tagPattern, "is").match(parserBuffer);
							if (tag.next()) {
								if (tag.group(2) == null) {// Open Tag
									CFMLTag cfmlTag = CFMLTagFactory.createTag(tag.group(3), tag.group(4), tagStartOffset);

									flushParserBuffer();

									if (cfmlTag != null) { // valid CFML
										cfmlTag.setStartLine(getLineNumber(tagStartOffset));

										if (tag.group(5).equalsIgnoreCase("/")) {
											cfmlTag.closeTag(fileOffset, getLineNumber(fileOffset), parserHandler);
										}
										if (tag.group(5) != null && tag.group(5).equalsIgnoreCase("/") && cfmlTag.requiresClosingTag()) {
											parserHandler.addMarker(new ParserException(cfmlTag.getClass().getSimpleName()
													+ " requires a closing tag", ParserException.SEVERITY_ERROR, getLineNumber(fileOffset)));
										} else {
											if (!tagStack.peek().addChild(cfmlTag)) {
												parserHandler.addMarker(new ParserException(
														cfmlTag.getClass().getSimpleName()
																+ " cannot be a child of "
																+ tagStack.peek().getClass().getSimpleName(),
														ParserException.SEVERITY_ERROR,
														getLineNumber(fileOffset)));
											} else if (cfmlTag.requiresClosingTag()) {
												tagStack.push(cfmlTag);
											}
										}
									} else {
										parserHandler.addMarker(new ParserException(
												"Not a CFML TAG: " + tag.group(3),
												ParserException.SEVERITY_ERROR,
												getLineNumber(fileOffset)));
									}
								} else {// Close tag
									if (CFMLTagFactory.getTagClass(tag.group(2)) == tagStack.peek().getClass()) {
										CFMLTag cfmlTag = tagStack.pop();
										cfmlTag.closeTag(fileOffset, getLineNumber(fileOffset), parserHandler);
									}
								}
							}
							currentState = NotInTag;
							break;
						}
					}
				}

				if (NotInTag == currentState) {
					for (int startTagIndex = 0; startTagIndex < startTag.length; startTagIndex++) {
						if (currentCharacter.charValue() == startTag[startTagIndex]) {
							currentState = InTag;
							parserBuffer.deleteCharAt(parserBuffer.length() - 1);
							flushParserBuffer();
							parserBuffer.append(currentCharacter);
							tagStartOffset = fileOffset;
						}
					}
				}
			}

			flushParserBuffer();
			if (InTag == (InTag & currentState)) {
				parserHandler.addMarker(new ParserException(
						"End of file reached, end of tag not found",
						ParserException.SEVERITY_ERROR,
						getLineNumber(input.length)));
			}

			if (tagStack.size() > 1) {
				for (int i = 1; i < tagStack.size(); i++) {
					parserHandler.addMarker(new ParserException(
							"Closing tag for " + tagStack.get(i).getClass().getSimpleName() + " not found",
							ParserException.SEVERITY_ERROR,
							tagStack.get(i).getStartLine()));
				}
			} else {
				tagStack.firstElement().validate(parserHandler);
			}

			rootTag = tagStack.pop();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return rootTag;
	}

	public Vector<Variable> getVariables(int offset) {
		if (rootTag == null)
			return null;
		return rootTag.getVariables(offset);
	}
}
